package _case

import (
	"errors"
	"fmt"
	"reflect"
)

// ReflectCase 函数演示了使用反射进行结构体拷贝的示例
func ReflectCase() {
	// 定义源结构体 user
	type user struct {
		ID    int64
		Name  string
		Hobby []string
	}
	// 定义目标结构体 outuser (字段与user相同)
	type outuser struct {
		ID    int64
		Name  string
		Hobby []string
	}

	// 创建源结构体实例并初始化
	u := user{
		ID:    1,
		Name:  "wangxud",
		Hobby: []string{"篮球", "羽毛球"},
	}
	// 创建目标结构体实例(零值)
	out := outuser{}

	// 调用copy函数将u拷贝到out
	res := copy(&out, u)
	// 打印拷贝结果和out的内容
	fmt.Println(res, out)
}

// copy 函数使用反射将一个结构体的值拷贝到另一个结构体
// dest: 目标对象(必须是指针)
// source: 源对象(可以是结构体或结构体指针)
func copy(dest interface{}, source interface{}) error {
	// 获取源对象的类型和值
	sType := reflect.TypeOf(source)
	sValue := reflect.ValueOf(source)

	// 如果源对象是指针类型，则获取其指向的类型和值
	if sType.Kind() == reflect.Ptr {
		sType = sType.Elem()   // 获取指针指向的类型
		sValue = sValue.Elem() // 获取指针指向的值
	}

	// 获取目标对象的类型和值
	dType := reflect.TypeOf(dest)
	dValue := reflect.ValueOf(dest)

	// 检查目标对象必须是指针类型
	if dType.Kind() != reflect.Ptr {
		return errors.New("目标对象必须为一个指针")
	}

	// 获取指针指向的类型和值
	dType = dType.Elem()
	dValue = dValue.Elem()

	// 检查源对象必须是结构体类型
	if sValue.Kind() != reflect.Struct {
		return errors.New("源对象必须为struct或者struct的指针")
	}

	// 检查目标对象必须是结构体类型
	if dValue.Kind() != reflect.Struct {
		return errors.New("目标对象必须为struct的指针")
	}

	// 创建一个新的目标类型实例(返回的是指针的reflect.Value)
	destObj := reflect.New(dType) // reflect.New总是返回指针类型

	// 遍历目标结构体的所有字段
	for i := 0; i < dType.NumField(); i++ {
		// 获取目标字段信息
		destFiled := dType.Field(i)

		// 尝试在源结构体中查找同名字段
		if sourceFiled, ok := sType.FieldByName(destFiled.Name); ok {
			// 如果字段类型不匹配，跳过
			if destFiled.Type != sourceFiled.Type {
				continue
			}
			// 获取源结构体中对应字段的值
			value := sValue.FieldByName(destFiled.Name)
			// 将值设置到新创建的目标结构体的对应字段
			destObj.Elem().FieldByName(destFiled.Name).Set(value)
		}
	}

	// 将新创建的结构体值设置到原始目标指针指向的位置
	dValue.Set(destObj.Elem())
	return nil
}
